/*
 * SPDX-FileCopyrightText: 2010-2022 Espressif Systems (Shanghai) CO LTD
 *
 * SPDX-License-Identifier: CC0-1.0
 */

#include <stdio.h>
#include <stdlib.h>
#include "sdkconfig.h"
#include <string.h>
#include <inttypes.h>
#include "fcntl.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_spiffs.h"
#include "esp_log.h"
#include "unistd.h"
#include "esp_vfs.h"
#include "esp_system.h"


#include "esp_system.h"
#include "esp_log.h"
#include "esp_console.h"
#include "driver/uart_vfs.h"
#include "driver/uart.h"
#include "linenoise/linenoise.h"
#include "argtable3/argtable3.h"
#include "esp_vfs_fat.h"
#include "nvs.h"
#include "nvs_flash.h"
#include "soc/soc_caps.h"
#include "cmd_system.h"
#include "cmd_wifi.h"
#include "cmd_nvs.h"

#include "esp_netif.h"

#include "lwip/err.h"
#include "lwip/sockets.h"
#include "lwip/sys.h"
#include <lwip/netdb.h>


#include "mqtt_client.h"



#include "lua.h"
#include "lualib.h"
#include "lauxlib.h"
#include "wifi_connect.h"
#include "tcp_server.h"
#include "ftp_server.h"
#include "telnet_server.h"

#include "lua_user_model.h"





static const char*TAG="esp32-spiffs";
static lua_State* createLua();

#define PROMPT_STR CONFIG_IDF_TARGET
lua_State*L;

int mopen(const char * path, int flags, int mode){
    printf("mopen %s,%d,%d\r\n",path,flags,mode);
    return 0;
}
ssize_t mread(int fd, void * dst, size_t size){
    printf("mread %d,%d\r\n",fd,size);
    return 0;
}
ssize_t mwrite(int fd, const void * data, size_t size){
    printf("mwrite %d,%d\r\n",fd,size);
    return 0;
}
int mclose(int fd){
    printf("mclose %d\r\n",fd);
    return 0;
}
int mfcntl(int fd, int cmd, int arg){
    printf("mfcntl %d %d %d\r\n",fd,cmd,arg);
    return 0;
}
const char* testPath="/dev/led";
esp_vfs_t vfsLed={
    .open=mopen,
    .read=mread,
    .close=mclose,
    .write=mwrite,
    .fcntl=mfcntl
};


void registDev(){

    esp_vfs_register(testPath,&vfsLed,NULL);
}
void spifsInit(){
     esp_vfs_spiffs_conf_t conf = {
      .base_path = "/spiffs",
      .partition_label = NULL,
      .max_files = 5,
      .format_if_mount_failed = true
    };

    esp_err_t ret = esp_vfs_spiffs_register(&conf);

    if (ret != ESP_OK) {
        if (ret == ESP_FAIL) {
            ESP_LOGE(TAG, "Failed to mount or format filesystem");
        } else if (ret == ESP_ERR_NOT_FOUND) {
            ESP_LOGE(TAG, "Failed to find SPIFFS partition");
        } else {
            ESP_LOGE(TAG, "Failed to initialize SPIFFS (%s)", esp_err_to_name(ret));
        }
        return;
    }

     size_t total = 0, used = 0;
    ret = esp_spiffs_info(conf.partition_label, &total, &used);
    if (ret != ESP_OK) {
        ESP_LOGE(TAG, "Failed to get SPIFFS partition information (%s). Formatting...", esp_err_to_name(ret));
        esp_spiffs_format(conf.partition_label);
        return;
    } else {
        ESP_LOGI(TAG, "Partition size: total: %d, used: %d", total, used);
    }

}

char buff[200]={0};


static void initialize_nvs(void)
{
    esp_err_t err = nvs_flash_init();
    if (err == ESP_ERR_NVS_NO_FREE_PAGES || err == ESP_ERR_NVS_NEW_VERSION_FOUND) {
        ESP_ERROR_CHECK( nvs_flash_erase() );
        err = nvs_flash_init();
    }
    ESP_ERROR_CHECK(err);
}

static void initialize_console(void)
{
    fflush(stdout);
    fsync(fileno(stdout));
    setvbuf(stdin, NULL, _IONBF, 0);
    uart_vfs_dev_port_set_rx_line_endings(CONFIG_ESP_CONSOLE_UART_NUM, ESP_LINE_ENDINGS_CR);
    uart_vfs_dev_port_set_tx_line_endings(CONFIG_ESP_CONSOLE_UART_NUM, ESP_LINE_ENDINGS_CRLF);
    const uart_config_t uart_config = {
            .baud_rate = CONFIG_ESP_CONSOLE_UART_BAUDRATE,
            .data_bits = UART_DATA_8_BITS,
            .parity = UART_PARITY_DISABLE,
            .stop_bits = UART_STOP_BITS_1,
#if SOC_UART_SUPPORT_REF_TICK
        .source_clk = UART_SCLK_REF_TICK,
#elif SOC_UART_SUPPORT_XTAL_CLK
        .source_clk = UART_SCLK_XTAL,
#endif
    };
    /* Install UART driver for interrupt-driven reads and writes */
    ESP_ERROR_CHECK( uart_driver_install(CONFIG_ESP_CONSOLE_UART_NUM,
            256, 0, 0, NULL, 0) );
    ESP_ERROR_CHECK( uart_param_config(CONFIG_ESP_CONSOLE_UART_NUM, &uart_config) );

    /* Tell VFS to use UART driver */
    uart_vfs_dev_use_driver(CONFIG_ESP_CONSOLE_UART_NUM);

    /* Initialize the console */
    esp_console_config_t console_config = {
            .max_cmdline_args = 8,
            .max_cmdline_length = 256,
#if CONFIG_LOG_COLORS
            .hint_color = atoi(LOG_COLOR_CYAN)
#endif
    };
    ESP_ERROR_CHECK( esp_console_init(&console_config) );

    linenoiseSetMultiLine(1);
    linenoiseSetCompletionCallback(&esp_console_get_completion);
    linenoiseSetHintsCallback((linenoiseHintsCallback*) &esp_console_get_hint);

    /* Set command history size */
    linenoiseHistorySetMaxLen(100);

    /* Set command maximum length */
    linenoiseSetMaxLineLen(console_config.max_cmdline_length);

    /* Don't return empty lines */
    linenoiseAllowEmpty(false);

#if CONFIG_STORE_HISTORY
    /* Load command history from filesystem */
    linenoiseHistoryLoad(HISTORY_PATH);
#endif
}


void cmdInit(lua_State*L ){


    //initialize_nvs();

    initialize_console();

    /* Register commands */
    esp_console_register_help_command();
    register_system_common();
    register_system_sleep();
    register_nvs();
    //ESP_ERROR_CHECK(esp_console_start_repl(repl));

    
     const char* prompt = LOG_COLOR_I PROMPT_STR "> " LOG_RESET_COLOR;

    printf("\n"
           "This is an example of ESP-IDF console component.\n"
           "Type 'help' to get the list of commands.\n"
           "Use UP/DOWN arrows to navigate through command history.\n"
           "Press TAB when typing command name to auto-complete.\n"
           "Press Enter or Ctrl+C will terminate the console environment.\n");

    /* Figure out if the terminal supports escape sequences */
    int probe_status = linenoiseProbe();
    if (probe_status) { /* zero indicates success */
        printf("\n"
               "Your terminal application does not support escape sequences.\n"
               "Line editing and history features are disabled.\n"
               "On Windows, try using Putty instead.\n");
        linenoiseSetDumbMode(1);
#if CONFIG_LOG_COLORS
        prompt = PROMPT_STR "> ";
#endif //CONFIG_LOG_COLORS
    }

    while(1){
        char* line = linenoise(prompt);
        if(line!=NULL&&strlen(line)>0){
            linenoiseHistoryAdd(line);

            int ret;
            esp_err_t err = esp_console_run(line, &ret);
            //当没有发现该命令时候，执行lua解析
            if (err == ESP_ERR_NOT_FOUND) {
                 if(strcmp("clear",line)==0){
                    printf("\x1b[2J\x1b[H");
                }else{
                    sprintf(buff,"%s\r\n",line);
                    const int ret =luaL_dostring(L,buff);
                    if(ret){
                        if (ret == LUA_ERRSYNTAX){
                            printf("\e[31;40m Error: %s\r\n", lua_tostring(L, -1));
                        }else if (ret == LUA_ERRMEM){
                            printf("\e[31;40m Error: memory error\r\n");
                        }else{
                            printf("\e[31;40m Error 2: %s\r\n",lua_tostring(L, -1));
                        }
                    }
                }
            }

            
           

            linenoiseFree(line);

        }
        vTaskDelay(20);
    }
    
}

void testDir(){
    printf("-------dir-------\r\n");
    DIR* dir = opendir("/spiffs/");
    if(dir!=NULL){
        do{
            struct dirent* de = readdir(dir);
            if (!de) {
                break;
            }
            //printf("\e[31;40m %s\r\n",de->d_name);
            printf("%s\r\n",de->d_name);
        }while(1);
        closedir(dir);
    }
    
}

static int lua_dir(lua_State*L){
    const char *path = luaL_checkstring(L, 1);
    char buffs[300];
    DIR* dir = opendir(path);
    int count=0;
    if(dir!=NULL){
        struct stat st;
        do{
            struct dirent* de = readdir(dir);
            if (!de) {
                break;
            }
            if(path[strlen(path)-1]=='/'){
                sprintf(buffs,"%s%s",path,de->d_name);
            }else{
                sprintf(buffs,"%s/%s",path,de->d_name);
            }
            stat(buffs, &st);
            printf("%s %ld\r\n",buffs,st.st_size);
            count++;
        }while(1);
        closedir(dir);
    }
    
    lua_pushinteger(L,count);
    return 0;
}

static int lua_print_table(lua_State*L){
    //esp_vfs_print();
    return 0;
}



// //下面的代码是用于数据从定向 
// void esp_vfs_print(){
//     //将串口数据重定向到网络
//     const int index1 = s_fd_table[1].vfs_index;
//     const int index2 = s_fd_table[55].vfs_index;
//     s_fd_table[1].vfs_index=index2;

//     s_fd_table[55].vfs_index=index1;

//     local_fd_t lf= s_fd_table[1].local_fd;
//     s_fd_table[1].local_fd=s_fd_table[55].local_fd;
//     s_fd_table[55].local_fd=lf;
// }


// //暂存串口的数据
// static int vfs_index_1;
// static local_fd_t local_fd_1;
// void esp_vfs_save_uart(){
//     vfs_index_1 = s_fd_table[1].vfs_index;
//     local_fd_1= s_fd_table[1].local_fd;
// }
// //替换串口
// void esp_vfs_replace_uart(int index){
//     s_fd_table[1].vfs_index=s_fd_table[index].vfs_index;
//      s_fd_table[1].local_fd=s_fd_table[index].local_fd;
// }

// void esp_vfs_reset_uart(){
//      s_fd_table[1].vfs_index=vfs_index_1;
//      s_fd_table[1].local_fd=local_fd_1;
// }
char*dd=NULL;

void test_task(void *pvParameters){

    printf("hello test task\r\n");
    while(1){
        vTaskDelay(25000/portTICK_PERIOD_MS);
    }
    
    vTaskDelete(NULL);
}

static int myfree(lua_State*L){
    int heap=esp_get_free_heap_size();
    lua_pushinteger(L,heap);
    return 1;
}

static int mysleep(lua_State*L){
    int time=luaL_checkinteger(L,1);
    vTaskDelay(time/portTICK_PERIOD_MS);
    return 0;
}


static void lua_run_task(void *pvParameters){
    char*name=(char*)pvParameters;
    lua_State*LL=createLua();


    int ret=luaL_dofile(LL,name);
    if(ret){
        if (ret == LUA_ERRSYNTAX)
            printf("\e[31;40m Error: %s\r\n", lua_tostring(LL, -1));
        else if (ret == LUA_ERRMEM)
            printf("\e[31;40m Error: memory error1\r\n");
        else{
            printf("\e[31;40m Error 2: %s\r\n",lua_tostring(LL, -1));
        }
    }

    lua_close(L);
    vTaskDelete(NULL);
}

static int myrun(lua_State*L){
    const char*name=luaL_checkstring(L,1);
    xTaskCreate(lua_run_task,name, 4096, (void*)name, 5, NULL);
    return 0;
}



#define CONFIG_BROKER_URL "mqtt://192.168.3.70:1883"



static void log_error_if_nonzero(const char *message, int error_code)
{
    if (error_code != 0) {
        ESP_LOGE(TAG, "Last error %s: 0x%x", message, error_code);
    }
}
static void mqtt_event_handler(void *handler_args, esp_event_base_t base, int32_t event_id, void *event_data)
{
    printf("---------call-------\r\n");
     ESP_LOGI(TAG, "Event dispatched from event loop base=%s, event_id=%" PRIi32 "", base, event_id);
     esp_mqtt_event_handle_t event = event_data;
    esp_mqtt_client_handle_t client = event->client;
    int msg_id;
    switch ((esp_mqtt_event_id_t)event_id) {
    case MQTT_EVENT_CONNECTED:
        ESP_LOGI(TAG, "MQTT_EVENT_CONNECTED");
        msg_id = esp_mqtt_client_publish(client, "/topic/qos1", "data_3", 0, 1, 0);
        ESP_LOGI(TAG, "sent publish successful, msg_id=%d", msg_id);

        msg_id = esp_mqtt_client_subscribe(client, "/topic/qos0", 0);
        ESP_LOGI(TAG, "sent subscribe successful, msg_id=%d", msg_id);

        msg_id = esp_mqtt_client_subscribe(client, "/topic/qos1", 1);
        ESP_LOGI(TAG, "sent subscribe successful, msg_id=%d", msg_id);

        msg_id = esp_mqtt_client_unsubscribe(client, "/topic/qos1");
        ESP_LOGI(TAG, "sent unsubscribe successful, msg_id=%d", msg_id);
        break;
    case MQTT_EVENT_DISCONNECTED:
        ESP_LOGI(TAG, "MQTT_EVENT_DISCONNECTED");
        break;

    case MQTT_EVENT_SUBSCRIBED:
        ESP_LOGI(TAG, "MQTT_EVENT_SUBSCRIBED, msg_id=%d", event->msg_id);
        msg_id = esp_mqtt_client_publish(client, "/topic/qos0", "data", 0, 0, 0);
        ESP_LOGI(TAG, "sent publish successful, msg_id=%d", msg_id);
        break;
    case MQTT_EVENT_UNSUBSCRIBED:
        ESP_LOGI(TAG, "MQTT_EVENT_UNSUBSCRIBED, msg_id=%d", event->msg_id);
        break;
    case MQTT_EVENT_PUBLISHED:
        ESP_LOGI(TAG, "MQTT_EVENT_PUBLISHED, msg_id=%d", event->msg_id);
        break;
    case MQTT_EVENT_DATA:
        ESP_LOGI(TAG, "MQTT_EVENT_DATA");
        printf("TOPIC=%.*s\r\n", event->topic_len, event->topic);
        printf("DATA=%.*s\r\n", event->data_len, event->data);
        break;
    case MQTT_EVENT_ERROR:
        ESP_LOGI(TAG, "MQTT_EVENT_ERROR");
        if (event->error_handle->error_type == MQTT_ERROR_TYPE_TCP_TRANSPORT) {
            log_error_if_nonzero("reported from esp-tls", event->error_handle->esp_tls_last_esp_err);
            log_error_if_nonzero("reported from tls stack", event->error_handle->esp_tls_stack_err);
            log_error_if_nonzero("captured as transport's socket errno",  event->error_handle->esp_transport_sock_errno);
            ESP_LOGI(TAG, "Last errno string (%s)", strerror(event->error_handle->esp_transport_sock_errno));

        }
        break;
    default:
        ESP_LOGI(TAG, "Other event id:%d", event->event_id);
        break;
    }
}




void initMqtt(){

    printf("---------initMqtt-------\r\n");
    esp_mqtt_client_config_t mqtt_cfg = {
        .broker.address.uri = CONFIG_BROKER_URL,
        .credentials.username="zhang",
        .credentials.authentication.password="123456",
    };

    esp_mqtt_client_handle_t client = esp_mqtt_client_init(&mqtt_cfg);
    /* The last argument may be used to pass data to the event handler, in this example mqtt_event_handler */
    esp_mqtt_client_register_event(client, ESP_EVENT_ANY_ID, mqtt_event_handler, NULL);
    esp_mqtt_client_start(client);

}


static lua_State* createLua(){
    lua_State*L = luaL_newstate();
    if(L==NULL){
        printf("lua vm error!\r\n");
        return NULL;
    }
    //luaopen_testbyte(L);
    lua_register(L, "free", myfree);
    lua_register(L, "sleep", mysleep);
    lua_register(L, "run", myrun);
    lua_openuserlibs(L);
    luaL_openlibs(L);
    lua_register(L, "dir", lua_dir);
    lua_register(L, "print_table", lua_print_table);
    return L;
}



void app_main(void)
{
    spifsInit();
    //testDir();
    registDev();
    initialize_nvs();
    connectWifi("biaodiankeji","bdkj6666");
    esp_vfs_save_uart();
    xTaskCreate(ftp_server_task, "ftp_server", 4096, (void*)AF_INET, 5, NULL);
    //xTaskCreate(telnet_server_task, "tcp_server", 4096, (void*)AF_INET, 5, NULL);
    //xTaskCreate(test_task, "test_task", 4096, NULL, 5, NULL);

    //xTaskCreate(test_task, "test_task", 4096, NULL, 5, NULL);

    //printf("hello start---------------vfs:%d\r\n",esp_vfs_max_count());
    
    L = createLua();
    
    int ret=luaL_dofile(L,"/spiffs/init.lua");
    if(ret){
        if (ret == LUA_ERRSYNTAX)
            printf("\e[31;40m Error: %s\r\n", lua_tostring(L, -1));
        else if (ret == LUA_ERRMEM)
            printf("\e[31;40m Error: memory error1\r\n");
        else{
            printf("\e[31;40m Error 2: %s\r\n",lua_tostring(L, -1));
        }
            
    }

    // //测试内存问题
    // TaskHandle_t handler;
    // int heap=0;
    // for(int i=0;i<100;i++){
    //     heap=esp_get_free_heap_size();
    //     printf("-------------------- %d\r\n",i);
    //     printf("start %d\r\n",heap);
    //     xTaskCreate(test_task, "test_task", 4096, NULL, 5, &handler);
    //     heap=esp_get_free_heap_size();
    //     printf("create %d\r\n",heap);
    //     vTaskDelay(1500/portTICK_PERIOD_MS);
    //     vTaskSuspend(handler);
    //     vTaskDelete(handler);
    //     heap=esp_get_free_heap_size();
    //     printf("delete:%d\r\n\r\n",heap);
    // }
    
    cmdInit(L);
    while(1){
        //printf("Hello world lua test!\n");
        vTaskDelay(200);
    }
}
